#!/usr/bin/env bash

FUNCTIONAL=${FUNCTIONAL:-functional}
export FUNCTIONAL

################################################################################
# Creates the new file descriptor "3" and redirects it to stdout, then redirects
# stderr e stdout to /dev/null. This way only the stuff we redirect to fd 3 is
# printed simplifying the work required to filter our output.
# Arguments:
#   None
################################################################################
function redirect_std() {
	exec 3>&1
	exec 1>/dev/null
	exec 2>&1
}

################################################################################
# Calls echo on the correct file descriptor (it needs to match the new file
# descriptor created by redirect_std()).
# Arguments:
#   None
################################################################################
function stdout_echo() {
	echo "$@"
}

################################################################################
# Closes the file descriptor server.
# Arguments:
#   None
################################################################################
function close_fd_server() {
	$FUNCTIONAL -c
	check_success "$?" "close_fd_server"
}

################################################################################
# Starts the file descriptor server.
# Arguments:
#   None
################################################################################
function start_fd_server() {
	$FUNCTIONAL -o
	check_success "$?" "start_fd_server"
}

################################################################################
# Restarts the file descriptor server.
# Arguments:
#   None
################################################################################
function restart_fd_server() {
	close_fd_server
	start_fd_server
}

################################################################################
# Can be called, when a test script successfully terminates, to perform a
# specific action.
# Arguments:
#   $1 -> string containing the test script name
################################################################################
function test_successful() {
	local test_name="$1"
	stdout_echo "$test_name: success."
}

################################################################################
# Checks the exit value, if it's different from the expected exit value
# terminates the script.
# Arguments:
#   $1 -> exit value to check
#   $2 -> expected exit value to check against
#   $3 -> string printed
#   $4 -> string containing the test script name
################################################################################
function check_exit() {
	local exit_value="$1"
	local expected_value="$2"
	local string_to_print="$3"
	local test_name="$0"

	if [ $exit_value != $expected_value ] ; then
		stdout_echo "$test_name: $string_to_print FAIL($exit_value != $expected_value)."
		exit 1
	fi
}

################################################################################
# Checks the exit value, if it's different from 0 terminates the script.
# Arguments:
#   $1 -> exit value to check
#   $2 -> string printed
################################################################################
function check_success() {
	local exit_value="$1"
	local string_to_print="$2"

	check_exit "$exit_value" 0 "$string_to_print"
}

################################################################################
# Checks the exit value, if it's different from 1 (only 1 is considered failure
# at the moment) terminates the script.
# Arguments:
#   $1 -> exit value to check
#   $2 -> string printed
################################################################################
function check_failure() {
	local exit_value="$1"
	local string_to_print="$2"

	check_exit "$exit_value" 1 "$string_to_print"
}

################################################################################
# Set a trap while maintaining the one currently set. The new one will be
# executed first.
# Arguments:
#   $1 -> new command to execute during the trap
#   $2 -> signal to trap
################################################################################
function cumulative_trap() {
	local new_command="$1"
	local signal="$2"
	local current_command=""

	# If we run "trap -p SIGNAL" in a subshell we read the traps for that
	# subshell, instead of the current one.
	# https://unix.stackexchange.com/a/334593
	shopt -s lastpipe
	trap -p "$signal" | read current_command
	shopt -u lastpipe

	current_command="$(echo $current_command | awk -F\' '{print $2}')"
	new_command="$new_command; $current_command"
	trap "$new_command" "$signal"
}

################################################################################
# Creates a VALE persistent port and sets a cleanup handler which will be
# called when the script exits.
# Arguments:
#   $1 -> name of the VALE persistent port
#   $2 -> expected exit value of vale-ctl (optional, default = 0)
################################################################################
function create_vale_persistent_port() {
	local if_name="$1"
	local create_exit_value="$2"
	create_exit_value="${create_exit_value:-0}"

	vale-ctl -n "$if_name"
	check_exit "$?" "$create_exit_value" "create $if_name"
	cumulative_trap "vale-ctl -r $if_name" "EXIT"
	check_success "$?" "trap-remove $if_name"
}

################################################################################
# Destroys a VALE persistent port.
# Arguments:
#   $1 -> name of the VALE persistent port
#   $2 -> expected exit value of vale-ctl (optional, default = 0)
################################################################################
function destroy_vale_persistent_port() {
	local if_name="$1"
	local destroy_exit_value="$2"
	destroy_exit_value="${destroy_exit_value:-0}"

	vale-ctl -r "$if_name"
	check_exit "$?" "$destroy_exit_value" "create $if_name"
}

################################################################################
# Attaches an interface registered to the os to a VALE bridge and sets a cleanup
# handler which will be called when the script exits.
# Arguments:
#   $1 -> name of the VALE bridge that the port will be to be attached to
#   $2 -> name of the interface
#   $3 -> expected exit value of vale-ctl (optional, default = 0)
################################################################################
function attach_to_vale_bridge() {
	local bdg_name="$1"
	local if_name="$2"
	local attach_exit_value="$3"
	attach_exit_value="${attach_exit_value:-0}"

	vale-ctl -a "$bdg_name:$if_name"
	check_exit "$?" "$attach_exit_value" "attach $bdg_name:$if_name"
	cumulative_trap "vale-ctl -d $bdg_name:$if_name" "EXIT"
	# We first need to close the file descriptor of the interface, otherwise
	# the detach will fail. To accomplish that we shut down fd_server.
	cumulative_trap "close_fd_server" "EXIT"
	check_success "$?" "trap-detach $bdg_name:$if_name"
}

################################################################################
# Detaches an interface registered to the os from a VALE bridge.
# Arguments:
#   $1 -> name of the VALE bridge that the port will be to be attached to
#   $2 -> name of the interface
#   $3 -> expected exit value of vale-ctl (optional, default = 0)
################################################################################
function detach_from_vale_bridge() {
	local bdg_name="$1"
	local if_name="$2"
	local detach_exit_value="$3"
	detach_exit_value="${detach_exit_value:-0}"

	vale-ctl -d "$bdg_name:$if_name"
	check_exit "$?" "$detach_exit_value" "detach $bdg_name:$if_name"
}

################################################################################
# Creates a pair of veth interfaces and sets a cleanup handler which will be
# called when the script exits.
# Arguments:
#   $1 -> base name of the veth devices
################################################################################
function create_veth_interfaces() {
	local if_name="$1"
	local if_name1="${if_name}A"
	local if_name2="${if_name}B"

	ip link add "$if_name1" type veth peer name "$if_name2"
	check_success "$?" "create $if_name"
	# We first need to close the file descriptor of the interfaces,
	# otherwise the delete will fail. To accomplish that we shut down
	# fd_server.
	cumulative_trap "ip link delete $if_name1" "EXIT"
	check_success "$?" "trap-delete $if_name1"
	cumulative_trap "close_fd_server" "EXIT"
	check_success "$?" "trap-detach $bdg_name:$if_name"
}

################################################################################
# Prints accepted command line arguments.
# Arguments:
#   None
################################################################################
function send_recv_usage() {
	stdout_echo "usage: [-h] "
	stdout_echo "       [-l packet_length]"
	stdout_echo "       [-f fill_character]"
	stdout_echo "       [-n packets_to_send]"
	stdout_echo "       [-q (sequential send/read)]"
	stdout_echo "       [-v (increases verbosity level)]"
}

################################################################################
# Parse the command line arguments of the calling script.
# Arguments:
#   It must be always called like like this 'parse_arguments "$@"'
################################################################################
function parse_send_recv_arguments() {
	while getopts "hvql:f:n:" opt; do
		case $opt in
			l) len="$OPTARG"
			;;
			f) fill="$OPTARG"
			;;
			n) num="$OPTARG"
			;;
			q) seq="-q"
			;;
			v) if [ -z $verbosity ] ; then
					verbosity="-v"
				else
					verbosity="${verbosity}v"
			   fi
			;;
			h) send_recv_usage ; exit 0
			;;
			\?) send_recv_usage ; exit 1
			;;
		esac
	done
}

################################################################################
# Prints to stdout the hexadecimal representation of the received integer.
# Arguments:
#   $1 -> integer to translate
################################################################################
# https://superuser.com/a/218349
int_to_hex() {
	local my_int=$1
	echo $(echo "obase=16; $my_int" | bc)
}

################################################################################
# Prints to stdout a random MAC address. FF:FF:FF:FF:FF:FF and 0:0:0:0:0:0 are
# excluded.
# Arguments:
#   None
################################################################################
# https://superuser.com/a/218349
function get_random_MAC() {
	local range=256
	local MAC1=$(int_to_hex $((RANDOM % range)))
	local MAC2=$(int_to_hex $((RANDOM % range)))
	local MAC3=$(int_to_hex $((RANDOM % range)))
	local MAC4=$(int_to_hex $((RANDOM % range)))
	local MAC5=$(int_to_hex $((RANDOM % range)))
	local MAC6=$(int_to_hex $((RANDOM % range)))
	local MAC="${MAC1}:${MAC2}:${MAC3}:${MAC4}:${MAC5}:${MAC6}"
	if [ $MAC = "FF:FF:FF:FF:FF:FF" ] || [ $MAC = "0:0:0:0:0:0" ] ; then
		get_random_MAC
	else
		echo "$MAC"
	fi
}

################################################################################
# Loads the netmap module, if needed.
# Arguments:
#   None
################################################################################
function netmap_load() {
	os=$(uname -s)
	case $os in
		Linux)
			modprobe netmap
			;;
		FreeBSD)
			# Nothing to do. We assume it is built in-kernel.
			;;
		*)
			echo "$os not supported"
			exit 1
			;;
	esac
}

################################################################################
# Unloads the netmap module, if needed.
# Arguments:
#   None
################################################################################
function netmap_unload() {
	os=$(uname -s)
	case $os in
		Linux)
			rmmod netmap
			;;
		FreeBSD)
			# Nothing to do.
			;;
		*)
			echo "$os not supported"
			exit 1
			;;
	esac
}
